﻿using System;
using System.Security.Permissions;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Windows.Forms;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using ConfigurationSettings;
using FileSystemControls.Support;

namespace FileSystemControls.ListViewItems
{

    public class FileListViewItem : FileSystemListViewItem
    {

        #region ** Member Variables **

        private FileInfo _thisFile;
        //private ProgressBarEx _progressBar;
        CancellationTokenSource _copyTokenSource;
        private string _nameWithoutExtention;

        #endregion

        #region ** Properties **

        public new bool IsReadOnly
        {
            get
            {
                return _isReadOnly;
            }

            set
            {
                if (value)
                {
                    File.SetAttributes(_fullPath, File.GetAttributes(_fullPath) | FileAttributes.ReadOnly);
                }
                else
                {
                    File.SetAttributes(_fullPath, File.GetAttributes(_fullPath) & ~FileAttributes.ReadOnly);
                }

                _thisFile.Refresh();

                _isReadOnly = (_thisFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
                _isHidden = (_thisFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
                _isArchive = (_thisFile.Attributes & FileAttributes.Archive) == FileAttributes.Archive;
                _isSystem = (_thisFile.Attributes & FileAttributes.System) == FileAttributes.System;
                _isCompressed = (_thisFile.Attributes & FileAttributes.Compressed) == FileAttributes.Compressed;
                _isEncrypted = (_thisFile.Attributes & FileAttributes.Encrypted) == FileAttributes.Encrypted;
                _isOffline = (_thisFile.Attributes & FileAttributes.Offline) == FileAttributes.Offline;
                _isDevice = (_thisFile.Attributes & FileAttributes.Device) == FileAttributes.Device;

            }
        }

        public new bool IsHidden
        {
            get
            {
                return _isHidden;
            }
            set
            {
                if (value)
                {
                    File.SetAttributes(_fullPath, File.GetAttributes(_fullPath) | FileAttributes.Hidden);
                }
                else
                {
                    File.SetAttributes(_fullPath, File.GetAttributes(_fullPath) & ~FileAttributes.Hidden);
                }

                _thisFile.Refresh();

                _isReadOnly = (_thisFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
                _isHidden = (_thisFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
                _isArchive = (_thisFile.Attributes & FileAttributes.Archive) == FileAttributes.Archive;
                _isSystem = (_thisFile.Attributes & FileAttributes.System) == FileAttributes.System;
                _isCompressed = (_thisFile.Attributes & FileAttributes.Compressed) == FileAttributes.Compressed;
                _isEncrypted = (_thisFile.Attributes & FileAttributes.Encrypted) == FileAttributes.Encrypted;
                _isOffline = (_thisFile.Attributes & FileAttributes.Offline) == FileAttributes.Offline;
                _isDevice = (_thisFile.Attributes & FileAttributes.Device) == FileAttributes.Device;

            }
        }

        public new bool IsArchive
        {
            get
            {
                return _isArchive;
            }
            set
            {
                if (value)
                {
                    File.SetAttributes(_fullPath, File.GetAttributes(_fullPath) | FileAttributes.Archive);
                }
                else
                {
                    File.SetAttributes(_fullPath, File.GetAttributes(_fullPath) & ~FileAttributes.Archive);
                }

                _thisFile.Refresh();

                _isReadOnly = (_thisFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
                _isHidden = (_thisFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
                _isArchive = (_thisFile.Attributes & FileAttributes.Archive) == FileAttributes.Archive;
                _isSystem = (_thisFile.Attributes & FileAttributes.System) == FileAttributes.System;
                _isCompressed = (_thisFile.Attributes & FileAttributes.Compressed) == FileAttributes.Compressed;
                _isEncrypted = (_thisFile.Attributes & FileAttributes.Encrypted) == FileAttributes.Encrypted;
                _isOffline = (_thisFile.Attributes & FileAttributes.Offline) == FileAttributes.Offline;
                _isDevice = (_thisFile.Attributes & FileAttributes.Device) == FileAttributes.Device;
            }
        }

        public new bool IsSystem
        {
            get
            {
                return _isSystem;
            }
            set
            {
                if (value)
                {
                    File.SetAttributes(_fullPath, File.GetAttributes(_fullPath) | FileAttributes.System);
                }
                else
                {
                    File.SetAttributes(_fullPath, File.GetAttributes(_fullPath) & ~FileAttributes.System);
                }

                _thisFile.Refresh();

                _isReadOnly = (_thisFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
                _isHidden = (_thisFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
                _isArchive = (_thisFile.Attributes & FileAttributes.Archive) == FileAttributes.Archive;
                _isSystem = (_thisFile.Attributes & FileAttributes.System) == FileAttributes.System;
                _isCompressed = (_thisFile.Attributes & FileAttributes.Compressed) == FileAttributes.Compressed;
                _isEncrypted = (_thisFile.Attributes & FileAttributes.Encrypted) == FileAttributes.Encrypted;
                _isOffline = (_thisFile.Attributes & FileAttributes.Offline) == FileAttributes.Offline;
                _isDevice = (_thisFile.Attributes & FileAttributes.Device) == FileAttributes.Device;
            }
        }

        public new bool IsCompressed
        {
            get
            {
                return _isCompressed;
            }
            set
            {
                short CompressionFlag;
                int lpBytesReturned = 0;
                int FSCTL_SET_COMPRESSION = 0x9C040;
                short COMPRESSION_FORMAT_NONE = 0;
                short COMPRESSION_FORMAT_DEFAULT = 1;

                if (value)
                {
                    CompressionFlag = COMPRESSION_FORMAT_DEFAULT;
                }
                else
                {
                    CompressionFlag = COMPRESSION_FORMAT_NONE;
                }

                //TODO: 022 - Test setting compression
                FileStream f = _thisFile.OpenWrite(); //FullFilePath, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite, System.IO.FileShare.None);

                int result = NativeMethods.DeviceIoControl(f.SafeFileHandle.DangerousGetHandle(), FSCTL_SET_COMPRESSION,
                    ref CompressionFlag, 2 /*sizeof(short)*/, IntPtr.Zero, 0, ref lpBytesReturned,
                    IntPtr.Zero);

                f.Close();

                _thisFile.Refresh();

                _isReadOnly = (_thisFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
                _isHidden = (_thisFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
                _isArchive = (_thisFile.Attributes & FileAttributes.Archive) == FileAttributes.Archive;
                _isSystem = (_thisFile.Attributes & FileAttributes.System) == FileAttributes.System;
                _isCompressed = (_thisFile.Attributes & FileAttributes.Compressed) == FileAttributes.Compressed;
                _isEncrypted = (_thisFile.Attributes & FileAttributes.Encrypted) == FileAttributes.Encrypted;
                _isOffline = (_thisFile.Attributes & FileAttributes.Offline) == FileAttributes.Offline;
                _isDevice = (_thisFile.Attributes & FileAttributes.Device) == FileAttributes.Device;

            }
        }

        public new bool IsEncrypted
        {
            get
            {
                return _isEncrypted;
            }
            set
            {
                //TODO: 020 - Set-up for directories too

                if (value)
                {
                    _thisFile.Encrypt();
                }
                else
                {
                    _thisFile.Decrypt();
                }

                _thisFile.Refresh();

                _isReadOnly = (_thisFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
                _isHidden = (_thisFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
                _isArchive = (_thisFile.Attributes & FileAttributes.Archive) == FileAttributes.Archive;
                _isSystem = (_thisFile.Attributes & FileAttributes.System) == FileAttributes.System;
                _isCompressed = (_thisFile.Attributes & FileAttributes.Compressed) == FileAttributes.Compressed;
                _isEncrypted = (_thisFile.Attributes & FileAttributes.Encrypted) == FileAttributes.Encrypted;
                _isOffline = (_thisFile.Attributes & FileAttributes.Offline) == FileAttributes.Offline;
                _isDevice = (_thisFile.Attributes & FileAttributes.Device) == FileAttributes.Device;
            }
        }

        public string ParentDirectory
        {
            get
            {
                return _thisFile.Directory.Parent.FullName;
            }
        }

        /// <summary>
        /// Length of the file this item refers to.
        /// </summary>
        public override long Length
        {
            get
            {
                return _fileLength;
            }
        }

        public override SizeUnitType FileSizeType
        {
            get
            {
                return _fileSizeType;
            }
            set
            {
                decimal DisplaySize;
                string DisplayFormat;

                _fileSizeType = value;
                switch (_fileSizeType)
                {
                    case SizeUnitType.Bytes:
                        _sizeMultiplier = 1; //2^1
                        _sizePostfix = " b";
                        break;
                    case SizeUnitType.Kilobytes:
                        _sizeMultiplier = 1024; //2^10
                        _sizePostfix = " Kb";
                        break;
                    case SizeUnitType.Megabytes:
                        _sizeMultiplier = 1048576; //2^20
                        _sizePostfix = " Mb";
                        break;
                    case SizeUnitType.Gigabytes:
                        _sizeMultiplier = 1073741824; //2^30
                        _sizePostfix = " Gb";
                        break;
                }

                DisplaySize = _fileLength / (decimal)_sizeMultiplier;

                if (_sizePrecision == 0)
                {
                    DisplayFormat = "#,##0";
                }
                else
                {
                    DisplayFormat = "#,##0." + new string('0', _sizePrecision);
                }

                this.SubItems[1].Text = Math.Round(DisplaySize, _sizePrecision).ToString(DisplayFormat) + _sizePostfix;
            }
        }


        #endregion

        #region ** Constructors **

        protected FileListViewItem()
        {
        }

        public FileListViewItem(FileInfo CurrentEntry)
            : this()
        {
            IntPtr iconHandle; //the handle to the system image list
            NativeMethods.SHFILEINFO shinfo = new NativeMethods.SHFILEINFO();

            //Use this to get the small Icon
            iconHandle = NativeMethods.SHGetFileInfo(CurrentEntry.FullName, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), NativeMethods.SHGFI_ICON | NativeMethods.SHGFI_SMALLICON | NativeMethods.SHGFI_TYPENAME);
            _smallIcon = System.Drawing.Icon.FromHandle(shinfo.hIcon);

            SetFileInfo(CurrentEntry);
        }

        #endregion

        #region ** Protected Methods **

        /// <summary>
        /// Sets the info for an item to information from the specified FileInfo object.
        /// </summary>
        /// <param name="argCurrentEntry">FileInfo containing the file this item represents.</param>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        protected void SetFileInfo(FileInfo argCurrentEntry)
        {
            string FileLength;
            FileVersionInfo CurrentVersionInfo;
            AssemblyName CurrentName;

            //FileInfo currentFileInfo = argCurrentEntry;
            if (argCurrentEntry.Exists)
            {
                Text = argCurrentEntry.Name;
                Name = argCurrentEntry.Name;

                _isFile = true;
                if (argCurrentEntry.Extension.Length > 0)
                {
                    _extension = argCurrentEntry.Extension.Substring(1); //Extension starts after the dot
                }
                else
                {
                    _extension = String.Empty;
                }

                _fileLength = argCurrentEntry.Length;
                Path = argCurrentEntry.DirectoryName;
                _fullPath = argCurrentEntry.FullName;
                RootName = argCurrentEntry.Name.Substring(0, argCurrentEntry.Name.Length - argCurrentEntry.Extension.Length); //End before the dot in the extension

                _isReadOnly = (argCurrentEntry.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
                _isHidden = (argCurrentEntry.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
                _isArchive = (argCurrentEntry.Attributes & FileAttributes.Archive) == FileAttributes.Archive;
                _isSystem = (argCurrentEntry.Attributes & FileAttributes.System) == FileAttributes.System;
                _isCompressed = (argCurrentEntry.Attributes & FileAttributes.Compressed) == FileAttributes.Compressed;
                _isEncrypted = (argCurrentEntry.Attributes & FileAttributes.Encrypted) == FileAttributes.Encrypted;
                _isOffline = (argCurrentEntry.Attributes & FileAttributes.Offline) == FileAttributes.Offline;
                _isDevice = (argCurrentEntry.Attributes & FileAttributes.Device) == FileAttributes.Device;

                CurrentVersionInfo = FileVersionInfo.GetVersionInfo(argCurrentEntry.FullName);

                //_comments = CurrentVersionInfo.Comments;
                //_companyName = CurrentVersionInfo.CompanyName;
                //_fileDescription = CurrentVersionInfo.FileDescription;
                if ((CurrentVersionInfo.FileMajorPart + CurrentVersionInfo.FileMinorPart + CurrentVersionInfo.FileBuildPart + CurrentVersionInfo.FilePrivatePart) > 0)
                {
                    _fileVersion = CurrentVersionInfo.FileMajorPart + "." + CurrentVersionInfo.FileMinorPart + "." + CurrentVersionInfo.FileBuildPart + "." + CurrentVersionInfo.FilePrivatePart;
                }
                else
                {
                    _fileVersion = null;
                }

                //_internalName = CurrentVersionInfo.InternalName;
                //_language = CurrentVersionInfo.Language;
                //_legalCopyright = CurrentVersionInfo.LegalCopyright;
                //_originalFilename = CurrentVersionInfo.OriginalFilename;
                //_productName = CurrentVersionInfo.ProductName;
                if ((CurrentVersionInfo.ProductMajorPart + CurrentVersionInfo.ProductMinorPart + CurrentVersionInfo.ProductBuildPart + CurrentVersionInfo.ProductPrivatePart) > 0)
                {
                    _productVersion = CurrentVersionInfo.ProductVersion;
                }
                else
                {
                    _productVersion = null;
                }

                try
                {
                    CurrentName = AssemblyName.GetAssemblyName(argCurrentEntry.FullName);
                    _assemblyVersion = CurrentName.Version.ToString();
                }
                catch (BadImageFormatException)
                {
                    //Don't worry. Not everything is a .NET assembly.
                    _assemblyVersion = String.Empty;
                }
                catch (FileLoadException)
                {
                    //Don't worry. Not everything is a .NET assembly.
                    _assemblyVersion = String.Empty;
                }

                _toolTip = "";
                //_toolTip = "Modified Date: " + CurrentFileInfo.LastWriteTime.ToShortDateString();
                //////_toolTip = "\r\nSize: " + (_fileLength / _sizeMultiplier).ToString("#,##0") + _sizePostfix;
                //_toolTip += "\r\nSize: " + GetSizeText(_fileLength, SizeUnitType.Bytes);

                //if (_comments != null)
                //{
                //    _toolTip += "\r\nComments: " + _comments;
                //}

                //if (_companyName != null)
                //{
                //    _toolTip += "\r\nCompany Name: " + _companyName;
                //}

                //if (_fileDescription != null)
                //{
                //    _toolTip += "\r\nDescription: " + _fileDescription;
                //}

                if (_fileVersion != null)
                {
                    _toolTip += "\r\nFile Version: " + _fileVersion;
                }

                //if (_internalName != null)
                //{
                //    _toolTip += "\r\nInternal Name: " + _internalName;
                //}

                //if (_language != null)
                //{
                //    _toolTip += "\r\nLanguage: " + _language;
                //}

                //if (_legalCopyright != null)
                //{
                //    _toolTip += "\r\nLegal Copyright: " + _legalCopyright;
                //}

                //if (_originalFilename != null)
                //{
                //    _toolTip += "\r\nOriginal Filename: " + _originalFilename;
                //}

                //if (_productName != null)
                //{
                //    _toolTip += "\r\nProduct Name: " + _productName;
                //}

                if (_productVersion != null)
                {
                    _toolTip += "\r\nProduct Version: " + _productVersion;
                }

                if (_assemblyVersion != null)
                {
                    _toolTip += "\r\nAssembly Version: " + _assemblyVersion;
                }

                FileLength = (_fileLength / _sizeMultiplier).ToString("#,##0") + _sizePostfix;

                SubItems.Add(FileLength);

                _thisFile = argCurrentEntry;

                _createDate = argCurrentEntry.CreationTime;
                _modifyDate = argCurrentEntry.LastWriteTime;

                SubItems.Add(_modifyDate.ToString());

            }
            else
            {
                throw new FileNotFoundException();
            }
        }

        #endregion

        #region ** Public Methods **

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        public override void Refresh()
        {
            _thisFile.Refresh();
            SetFileInfo(_thisFile);
        }

        public void Touch()
        {
            Touch(DateTime.Now);
        }

        public void Touch(DateTime NewDate)
        {
            //  Get the readOnly property
            bool originalReadOnly = _thisFile.IsReadOnly;

            //  Set ReadOnly = false
            _thisFile.IsReadOnly = false;

            //  update the LastWriteDate on the file
            _thisFile.LastWriteTime = NewDate;

            //  Set ReadOnly to original
            _thisFile.IsReadOnly = originalReadOnly;
        }

        public void SetCreateDate(DateTime NewDate)
        {
            //  Get the readOnly property
            bool originalReadOnly = _thisFile.IsReadOnly;

            //  Set ReadOnly = false
            _thisFile.IsReadOnly = false;

            //  update the LastWriteDate on the file
            _thisFile.CreationTime = NewDate;
            _thisFile.LastAccessTime = NewDate;
            _thisFile.LastWriteTime = NewDate;

            //  Set ReadOnly to original
            _thisFile.IsReadOnly = originalReadOnly;
        }

        public override void Rename(string argNewName)
        {
            Rename(argNewName, false);
        }

        public override void Rename(string argNewName, bool argForceAction)
        {
            if (Name == argNewName)
            {
                return;
            }

            //If this file is readOnly and we are not forcing the rename throw an exception
            if (_thisFile.IsReadOnly && !argForceAction)
            {
                throw new FileIsReadOnlyException(_thisFile);
            }

            //NewPath and OldName
            FileInfo DestFile = new FileInfo(Path + "\\" + argNewName);

            //If Destination exists and we are not forcing the rename then ask
            if (DestFile.Exists && !argForceAction)
            {
                throw new FileAlreadyExistsException(DestFile);
            }

            if (DestFile.Exists && DestFile.IsReadOnly && !argForceAction)
            {
                throw new FileIsReadOnlyException(DestFile);
            }

            if (DestFile.Exists)
            {
                DestFile.IsReadOnly = false;
                DestFile.Delete();
            }

            _thisFile.MoveTo(Path + "\\" + argNewName);
        }

        public void MoveTo(string NewPath)
        {
            MoveTo(NewPath, false);
        }

        public void MoveTo(string NewPath, bool ForceAction)
        {
            if (Path == NewPath)
            {
                throw new PathMismatchException("Source and destination paths cannot be the same.");
            }

            //If this file is readOnly and we are not forcing the move throw an exception
            if (_thisFile.IsReadOnly && !ForceAction)
            {
                throw new FileIsReadOnlyException(_thisFile);
            }

            //NewPath and OldName
            FileInfo DestFile = new FileInfo(NewPath + "\\" + Name);

            //If Destination exists and we are not forcing the move then ask
            if (DestFile.Exists && !ForceAction)
            {
                throw new FileAlreadyExistsException(DestFile);
            }

            if (DestFile.Exists && DestFile.IsReadOnly && !ForceAction)
            {
                throw new FileIsReadOnlyException(DestFile);
            }

            if (DestFile.Exists)
            {
                DestFile.IsReadOnly = false;
                DestFile.Delete();
            }

            _thisFile.MoveTo(NewPath + "\\" + Name);

        }

        ////TODO: 014 - Enable multithreaded Copy support for Files        
        public override void CopyTo(string argNewPath, bool argForceAction = false)
        {
            {
                string DestFileName;
                FileInfo DestFileInfo = null;

                try
                {                                        

                    //Get FileInfo for DestinationFile.
                    DestFileName = argNewPath + @"\" + this.Name;

                    if (File.Exists(DestFileName))
                    {
                        if (!argForceAction)
                        {
                            throw new FileExistsException();
                        }

                        DestFileInfo = new FileInfo(DestFileName);

                        //  Set ReadOnly = false
                        DestFileInfo.IsReadOnly = false;

                    }

                    _copyTokenSource = new CancellationTokenSource();
                    //CurrentProgressBar.Maximum = _fileLength;
                    try
                    {
                        Task.Factory.StartNew(() =>
                        {
                            DoCopyWork(DestFileName, _copyTokenSource.Token); //, ProgressMethod);
                        },
                            _copyTokenSource.Token);
                    }
                    catch (OperationCanceledException)
                    {
                        MessageBox.Show("Copy Canceled");
                    }

                }
                catch (IOException)
                {
                }
            }
        }

        protected void DoCopyWork(string DestFileName, CancellationToken CopyToken) 
        {
            {
                FileInfo DestInfo;
                int chunkSize = 4096;
                byte[] FileBuffer = new byte[chunkSize];
                int BytesRead = 0;
                //bool CopyCanceled = false;

                DestInfo = new FileInfo(DestFileName);

                //Check if the destination file exists
                if (DestInfo.Exists)
                {
                    DestInfo.Delete();
                }

                //Open source file for reading.
                //Change this to File.Open(,,,,FileShare.None) to isolate the file and keep it from being edited during the move.
                using (FileStream SourceFile = _thisFile.OpenRead())
                {

                    //Open Destination file for writing.
                    using (FileStream DestFile = DestInfo.OpenWrite())
                    {
                        while (SourceFile.Position < SourceFile.Length)
                        {
                            //Read a byte from the source file.
                            BytesRead = SourceFile.Read(FileBuffer, 0, chunkSize);

                            //Write the byte to the destination file.
                            DestFile.Write(FileBuffer, 0, BytesRead);

                            //Updat the progress bar.
                            OnCopyProgress(DestFile.Position);

                            //CopyCanceled = ProgressMethod(DestFile.Position);
                            if (CopyToken.IsCancellationRequested)
                            {
                                break;
                            }
                        }
                    }
                }

                if (File.Exists(DestFileName))
                {
                    DestInfo = new FileInfo(DestFileName);

                    //  Get the readOnly property
                    bool originalReadOnly = DestInfo.IsReadOnly;

                    //  Set ReadOnly = false
                    DestInfo.IsReadOnly = false;

                    //  update the LastWriteDate on the file
                    DestInfo.LastWriteTime = this._modifyDate;

                    //  Set ReadOnly to original
                    DestInfo.IsReadOnly = originalReadOnly;
                }

                if (CopyToken.IsCancellationRequested)
                {
                    //Clean up
                    File.Delete(DestFileName);

                    //Throw
                    CopyToken.ThrowIfCancellationRequested();
                }

                OnCopyComplete();
            }
        }

        public override void CopyCancel()
        {
            if (_copyTokenSource != null)
            {
                _copyTokenSource.Cancel();
            }
        }

        public void Duplicate()
        {
            _thisFile.CopyTo(Path + "\\Duplicate of " + Name, true);
        }

        public void Delete()
        {
            Delete(false);
        }

        public void Delete(bool ForceAction)
        {
            //Check ReadOnly Attrib
            if (_isReadOnly && !ForceAction)
            {
                throw new FileIsReadOnlyException(_thisFile);
            }

            if (_isSystem && !ForceAction)
            {
                throw new FileIsSystemException(_thisFile);
            }

            if (_isOffline && !ForceAction)
            {
                throw new FileIsOfflineException(_thisFile);
            }

            _thisFile.IsReadOnly = false;
            _thisFile.Delete();

        }

        #endregion

    }

}
